package logging

import (
	"context"
	"fmt"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport/grpc"
	"github.com/go-kratos/kratos/v2/transport/http"
	"go.opentelemetry.io/otel/trace"

	"open/pkg/log"
)

// Server is an server logging middleware.
func Server() middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			var (
				path      string
				method    string
				query     string
				args      string
				component string
				traceID   string
			)
			logger := log.FromContext(ctx)
			if stringer, ok := req.(fmt.Stringer); ok {
				args = stringer.String()
			} else {
				args = fmt.Sprintf("%+v", req)
			}
			span := trace.SpanContextFromContext(ctx)
			if span.HasTraceID() {
				traceID = span.TraceID().String()
			}
			if info, ok := http.FromServerContext(ctx); ok {
				logger = logger.With("trace_id", traceID)
				ctx = log.WithLogger(ctx, logger)
				component = "HTTP"
				path = info.Request.URL.Path
				method = info.Request.Method
				query = info.Request.URL.RawQuery
			} else if info, ok := grpc.FromServerContext(ctx); ok {
				component = "gRPC"
				path = info.FullMethod
				method = "POST"
			}
			reply, err := handler(ctx, req)
			if err != nil {
				logger.Errorw(
					"",
					"kind", "server",
					"component", component,
					"path", path,
					"method", method,
					"query", query,
					"req", req,
					"code", uint32(errors.Code(err)),
					"error", err.Error(),
				)
				return nil, err
			}
			logger.Infow(
				"",
				"kind", "server",
				"component", component,
				"path", path,
				"method", method,
				"query", query,
				"args", args,
				"code", 0,
			)
			return reply, nil
		}
	}
}

// Client is an client logging middleware.
func Client() middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			var (
				path      string
				method    string
				params    string
				component string
			)
			logger := log.FromContext(ctx)
			if info, ok := http.FromClientContext(ctx); ok {
				component = "HTTP"
				path = info.Request.RequestURI
				method = info.Request.Method
				params = info.Request.Form.Encode()
			} else if info, ok := grpc.FromClientContext(ctx); ok {
				path = info.FullMethod
				method = "POST"
				component = "gRPC"
				params = req.(fmt.Stringer).String()
			}
			reply, err := handler(ctx, req)
			if err != nil {
				logger.Errorw(
					"http/client",
					"kind", "client",
					"component", component,
					"path", path,
					"method", method,
					"params", params,
					"code", errors.Code(err),
					"error", err.Error(),
				)
				return nil, err
			}
			logger.Infow(
				"http/client",
				"kind", "client",
				"component", component,
				"path", path,
				"method", method,
				"params", params,
				"code", 0,
			)
			return reply, nil
		}
	}
}
